Add tga loader, patch by Nicola Girardi. (#56067)
authorMatthias Clasen <matthiasc@src.gnome.org>
Tue, 21 Aug 2001 08:51:06 +0000 (08:51 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Tue, 21 Aug 2001 08:51:06 +0000 (08:51 +0000)
13 files changed:
ChangeLog
ChangeLog.pre-2-0
ChangeLog.pre-2-10
ChangeLog.pre-2-2
ChangeLog.pre-2-4
ChangeLog.pre-2-6
ChangeLog.pre-2-8
configure.in
gdk-pixbuf/ChangeLog
gdk-pixbuf/Makefile.am
gdk-pixbuf/gdk-pixbuf-io.c
gdk-pixbuf/io-tga.c [new file with mode: 0644]
gdk-pixbuf/pixbufloader_tga.def [new file with mode: 0644]

index 4302f8a0f9bdc02dfdfcb5981cb9aeedeb5ca4fe..8ab11263ac0d6fc7c73650227eebe8c373c6a8c4 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * configure.in (all_loaders): Add tga loader, 
+       patch by Nicola Girardi. (#56067)
+
 Tue Aug 21 02:57:13 2001  Jonathan Blandford  <jrb@redhat.com>>
 
        * gtk/gtktreeview.c (gtk_tree_view_button_press): Fix up selection
index 4302f8a0f9bdc02dfdfcb5981cb9aeedeb5ca4fe..8ab11263ac0d6fc7c73650227eebe8c373c6a8c4 100644 (file)
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * configure.in (all_loaders): Add tga loader, 
+       patch by Nicola Girardi. (#56067)
+
 Tue Aug 21 02:57:13 2001  Jonathan Blandford  <jrb@redhat.com>>
 
        * gtk/gtktreeview.c (gtk_tree_view_button_press): Fix up selection
index 4302f8a0f9bdc02dfdfcb5981cb9aeedeb5ca4fe..8ab11263ac0d6fc7c73650227eebe8c373c6a8c4 100644 (file)
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * configure.in (all_loaders): Add tga loader, 
+       patch by Nicola Girardi. (#56067)
+
 Tue Aug 21 02:57:13 2001  Jonathan Blandford  <jrb@redhat.com>>
 
        * gtk/gtktreeview.c (gtk_tree_view_button_press): Fix up selection
index 4302f8a0f9bdc02dfdfcb5981cb9aeedeb5ca4fe..8ab11263ac0d6fc7c73650227eebe8c373c6a8c4 100644 (file)
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * configure.in (all_loaders): Add tga loader, 
+       patch by Nicola Girardi. (#56067)
+
 Tue Aug 21 02:57:13 2001  Jonathan Blandford  <jrb@redhat.com>>
 
        * gtk/gtktreeview.c (gtk_tree_view_button_press): Fix up selection
index 4302f8a0f9bdc02dfdfcb5981cb9aeedeb5ca4fe..8ab11263ac0d6fc7c73650227eebe8c373c6a8c4 100644 (file)
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * configure.in (all_loaders): Add tga loader, 
+       patch by Nicola Girardi. (#56067)
+
 Tue Aug 21 02:57:13 2001  Jonathan Blandford  <jrb@redhat.com>>
 
        * gtk/gtktreeview.c (gtk_tree_view_button_press): Fix up selection
index 4302f8a0f9bdc02dfdfcb5981cb9aeedeb5ca4fe..8ab11263ac0d6fc7c73650227eebe8c373c6a8c4 100644 (file)
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * configure.in (all_loaders): Add tga loader, 
+       patch by Nicola Girardi. (#56067)
+
 Tue Aug 21 02:57:13 2001  Jonathan Blandford  <jrb@redhat.com>>
 
        * gtk/gtktreeview.c (gtk_tree_view_button_press): Fix up selection
index 4302f8a0f9bdc02dfdfcb5981cb9aeedeb5ca4fe..8ab11263ac0d6fc7c73650227eebe8c373c6a8c4 100644 (file)
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * configure.in (all_loaders): Add tga loader, 
+       patch by Nicola Girardi. (#56067)
+
 Tue Aug 21 02:57:13 2001  Jonathan Blandford  <jrb@redhat.com>>
 
        * gtk/gtktreeview.c (gtk_tree_view_button_press): Fix up selection
index 98b30409fe955d8502862971d5702201c6588037..76dc6fda6d2abba371ffa5b4e693a75ab272e5d3 100644 (file)
@@ -524,7 +524,7 @@ else
    fi
 fi
 
-all_loaders="png,bmp,wbmp,gif,ico,jpeg,pnm,ras,tiff,xpm"
+all_loaders="png,bmp,wbmp,gif,ico,jpeg,pnm,ras,tiff,xpm,tga"
 included_loaders=""
 # If no loaders specified, include all
 if test "x$with_included_loaders" = xyes ; then
index 8785db5f1319ab6199f47a7fd204e2b9db7556b7..e36719def0ab348c04e0bcf2f21c414c663d2a95 100644 (file)
@@ -1,3 +1,8 @@
+2001-08-21  Matthias Clasen  <matthiasc@waldgeist.poet.de>
+
+       * Makefile.am, gdk-pixbuf-io.c, pixbufloader_tga.def, 
+       io-tga.c: Add tga loader, patch by Nicola Girardi. (#56067)
+
 2001-08-20  Matthias Clasen  <matthiasc@waldgeist.poet.de>
 
        * test-images.h, test-loaders.c: Add ico and wbmp tests.
index 374ea905a3bfe73d98ba2d7d429b6a1b376e4471..f1100f89eb36ebb0a318e3ebf946dfb764cf913d 100644 (file)
@@ -95,6 +95,14 @@ libpixbufloader_xbm_la_LDFLAGS = -version-info 1:0:0 -module
 libpixbufloader_xbm_la_LDFLAGS = -avoid-version -module
 libpixbufloader_xbm_la_LIBADD = $(module_libs)
 
+#
+# The TGA loader
+#
+libpixbufloader_tga_la_SOURCES = io-tga.c
+libpixbufloader_tga_la_LDFLAGS = -version-info 1:0:0 -module
+libpixbufloader_tga_la_LDFLAGS = -avoid-version -module
+libpixbufloader_tga_la_LIBADD = $(module_libs)
+
 if HAVE_PNG
 PNG_LIB = libpixbufloader-png.la
 endif
@@ -123,6 +131,8 @@ WBMP_LIB = libpixbufloader-wbmp.la
 
 XBM_LIB = libpixbufloader-xbm.la
 
+TGA_LIB = libpixbufloader-tga.la
+
 if BUILD_DYNAMIC_MODULES
 
 loader_LTLIBRARIES =   \
@@ -136,7 +146,8 @@ loader_LTLIBRARIES =        \
        $(PNM_LIB)      \
        $(BMP_LIB)      \
        $(WBMP_LIB)     \
-       $(XBM_LIB)
+       $(XBM_LIB)      \
+       $(TGA_LIB)
 
 
 extra_sources = 
index 2ffed40659018fa3cf8c90ede42ebbd384aca75b..aef06defc875965a7997683aaffe504b8bdd25d2 100644 (file)
@@ -200,6 +200,24 @@ pixbuf_check_xbm (guchar *buffer, int size)
        return TRUE;
 }
 
+static gboolean
+pixbuf_check_tga (guchar *buffer, int size)
+{
+        if (size < 18)
+                return FALSE;
+        /* buffer[1] is a boolean telling if in the file a colormap is
+           present, while buffer[2] is the byte which specifies the image
+           type. (GrayScale/PseudoColor/TrueColor/RLE) */
+        if ((buffer[2] == 1) || (buffer[2] == 9)) {
+                if (buffer[1] != 1)
+                        return FALSE;
+        } else {
+                if (buffer[1] != 0)
+                       return FALSE;
+        }
+        return TRUE;
+}
+
 static GdkPixbufModule file_formats [] = {
        { "png",  pixbuf_check_png, NULL,  NULL, NULL, NULL, NULL, NULL, NULL, },
        { "jpeg", pixbuf_check_jpeg, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
@@ -209,10 +227,13 @@ static GdkPixbufModule file_formats [] = {
        { "xpm",  pixbuf_check_xpm, NULL,  NULL, NULL, NULL, NULL, NULL, NULL },
        { "pnm",  pixbuf_check_pnm, NULL,  NULL, NULL, NULL, NULL, NULL, NULL },
        { "ras",  pixbuf_check_sunras, NULL,  NULL, NULL, NULL, NULL, NULL, NULL },
-       { "ico",  pixbuf_check_ico, NULL,  NULL, NULL, NULL, NULL, NULL, NULL },
        { "bmp",  pixbuf_check_bmp, NULL,  NULL, NULL, NULL, NULL, NULL, NULL },
        { "wbmp", pixbuf_check_wbmp, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
        { "xbm",  pixbuf_check_xbm, NULL,  NULL, NULL, NULL, NULL, NULL, NULL },
+       { "tga", pixbuf_check_tga, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+       /* Moved at the bottom, because it causes false positives against many
+          of my TGA files. */
+       { "ico",  pixbuf_check_ico, NULL,  NULL, NULL, NULL, NULL, NULL, NULL },
        { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
 };
 
@@ -336,6 +357,7 @@ m_fill_vtable (ras);
 m_fill_vtable (tiff);
 m_fill_vtable (xpm);
 m_fill_vtable (xbm);
+m_fill_vtable (tga);
 
 gboolean
 _gdk_pixbuf_load_module (GdkPixbufModule *image_module,
@@ -414,6 +436,11 @@ _gdk_pixbuf_load_module (GdkPixbufModule *image_module,
        }
 #endif
 
+#ifdef INCLUDE_tga
+       else if (strcmp (image_module->module_name, "tga") == 0){
+               fill_vtable = mname (tga, fill_vtable);
+       }
+#endif
         
         if (fill_vtable) {
                 (* fill_vtable) (image_module);
diff --git a/gdk-pixbuf/io-tga.c b/gdk-pixbuf/io-tga.c
new file mode 100644 (file)
index 0000000..1f84d5a
--- /dev/null
@@ -0,0 +1,1162 @@
+/* 
+ * GdkPixbuf library - TGA image loader
+ * Copyright (C) 1999 Nicola Girardi <nikke@swlibero.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Some NOTES about the TGA loader (2001/06/07, nikke@swlibero.org)
+ *
+ * - The module doesn't currently provide support for TGA images where the
+ *   order of the pixels isn't left-to-right and top-to-bottom.  I plan to
+ *   add support for those files as soon as I get one of them.  I haven't
+ *   run into one yet.  (And I don't seem to be able to create it with GIMP.)
+ *
+ * - The TGAFooter isn't present in all TGA files.  In fact, there's an older
+ *   format specification, still in use, which doesn't cover the TGAFooter.
+ *   Actually, most TGA files I have are of the older type.  Anyway I put the 
+ *   struct declaration here for completeness.
+ *
+ * - Error handling was designed to be very paranoid.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "gdk-pixbuf.h"
+#include "gdk-pixbuf-io.h"
+#include "gdk-pixbuf-private.h"
+
+#define TGA_INTERLEAVE_MASK     0xc0
+#define TGA_INTERLEAVE_NONE     0x00
+#define TGA_INTERLEAVE_2WAY     0x40
+#define TGA_INTERLEAVE_4WAY     0x80
+
+#define TGA_ORIGIN_MASK         0x30
+#define TGA_ORIGIN_LEFT         0x00
+#define TGA_ORIGIN_RIGHT        0x10
+#define TGA_ORIGIN_LOWER        0x00
+#define TGA_ORIGIN_UPPER        0x20
+
+enum {
+       TGA_TYPE_NODATA = 0,
+       TGA_TYPE_PSEUDOCOLOR = 1,
+       TGA_TYPE_TRUECOLOR = 2,
+       TGA_TYPE_GRAYSCALE = 3,
+       TGA_TYPE_RLE_PSEUDOCOLOR = 9,
+       TGA_TYPE_RLE_TRUECOLOR = 10,
+       TGA_TYPE_RLE_GRAYSCALE = 11
+};
+
+#define LE16(p) ((p)[0] + ((p)[1] << 8))
+
+typedef struct _IOBuffer IOBuffer;
+
+typedef struct _TGAHeader TGAHeader;
+typedef struct _TGAFooter TGAFooter;
+
+typedef struct _TGAColormap TGAColormap;
+typedef struct _TGAColor TGAColor;
+
+typedef struct _TGAContext TGAContext;
+
+struct _TGAHeader {
+       guint8 infolen;
+       guint8 has_cmap;
+       guint8 type;
+       
+       guint8 cmap_start[2];
+       guint8 cmap_n_colors[2];
+       guint8 cmap_bpp;
+       
+       guint8 x_origin[2];
+       guint8 y_origin[2];
+       
+       guint8 width[2];
+       guint8 height[2];
+       guint8 bpp;
+       
+       guint8 flags;
+};
+
+struct _TGAFooter {
+       guint32 extension_area_offset;
+       guint32 developer_directory_offset;
+
+       /* Standard TGA signature, "TRUEVISION-XFILE.\0". */
+       union {
+               gchar sig_full[18];
+               struct {
+                       gchar sig_chunk[16];
+                       gchar dot, null;
+               } sig_struct;
+       } sig;
+};
+
+struct _TGAColormap {
+       gint size;
+       TGAColor *cols;
+};
+
+struct _TGAColor {
+       guchar r, g, b, a;
+};
+
+struct _TGAContext {
+       TGAHeader *hdr;
+       guint rowstride;
+       gboolean run_length_encoded;
+
+       TGAColormap *cmap;
+       guint cmap_size;
+
+       GdkPixbuf *pbuf;
+       guint pbuf_bytes;
+       guint pbuf_bytes_done;
+       guchar *pptr;
+
+       IOBuffer *in;
+
+       gboolean skipped_info;
+       gboolean prepared;
+       gboolean done;
+
+       ModulePreparedNotifyFunc pfunc;
+       ModuleUpdatedNotifyFunc ufunc;
+       gpointer udata;
+};
+
+struct _IOBuffer {
+       guchar *data;
+       guint size;
+};
+
+static IOBuffer *io_buffer_new(GError **err)
+{
+       IOBuffer *buffer;
+       buffer = g_try_malloc(sizeof(IOBuffer));
+       if (!buffer) {
+               g_set_error(err, GDK_PIXBUF_ERROR,
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate memory for IOBuffer struct"));
+               return NULL;
+       }
+       buffer->data = NULL;
+       buffer->size = 0;
+       return buffer;
+}
+
+static IOBuffer *io_buffer_append(IOBuffer *buffer, 
+                                 const guchar *data, guint len, 
+                                 GError **err)
+{
+       if (!buffer)
+               return NULL;
+       if (!buffer->data) {
+               buffer->data = g_try_malloc(len);
+               if (!buffer->data) {
+                       g_set_error(err, GDK_PIXBUF_ERROR,
+                                   GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                   _("Can't allocate memory for IOBuffer data"));
+                       g_free(buffer);
+                       return NULL;
+               }
+               g_memmove(buffer->data, data, len);
+               buffer->size = len;
+       } else {
+               buffer->data = g_try_realloc(buffer->data, buffer->size + len);
+               if (!buffer->data) {
+                       g_set_error(err, GDK_PIXBUF_ERROR,
+                                   GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                   _("Can't realloc IOBuffer data"));
+                       g_free(buffer);
+                       return NULL;
+               }
+               g_memmove(&buffer->data[buffer->size], data, len);
+               buffer->size += len;
+       }
+       return buffer;
+}
+
+static IOBuffer *io_buffer_free_segment(IOBuffer *buffer, 
+                                       guint count,
+                                        GError **err)
+{
+       g_return_val_if_fail(buffer != NULL, NULL);
+       g_return_val_if_fail(buffer->data != NULL, NULL);
+       if (count == buffer->size) {
+               g_free(buffer->data);
+               buffer->data = NULL;
+               buffer->size = 0;
+       } else {
+               guchar *new_buf;
+               guint new_size;
+
+               new_size = buffer->size - count;
+               new_buf = g_try_malloc(new_size);
+               if (!new_buf) {
+                       g_set_error(err, GDK_PIXBUF_ERROR,
+                                   GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                   _("Can't allocate temporary IOBuffer data"));
+                       g_free(buffer->data);
+                       g_free(buffer);
+                       return NULL;
+               }
+
+               g_memmove(new_buf, &buffer->data[count], new_size);
+               g_free(buffer->data);
+               buffer->data = new_buf;
+               buffer->size = new_size;
+       }
+       return buffer;
+}
+
+static void io_buffer_free(IOBuffer *buffer)
+{
+       g_return_if_fail(buffer != NULL);
+       if (buffer->data)
+               g_free(buffer->data);
+       g_free(buffer);
+}
+
+static void free_buffer(guchar *pixels, gpointer data)
+{
+       g_free(pixels);
+}
+
+static gboolean fread_check(gpointer dest, 
+                           size_t size, size_t count, 
+                           FILE *f, GError **err)
+{
+       if (fread(dest, size, count, f) != count) {
+               g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
+                           _("fread() failed -- premature end-of-file probably encountered"));
+               return FALSE;
+       }
+       return TRUE;
+}
+
+static gboolean fseek_check(FILE *f, glong offset, gint whence, GError **err)
+{
+       if (fseek(f, offset, whence) != 0) {
+               g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
+                           _("fseek() failed -- premature end-of-file probably encountered"));
+               return FALSE;
+       }
+       return TRUE;
+}
+
+static gboolean fill_in_context(TGAContext *ctx, GError **err)
+{
+       gboolean alpha;
+
+       g_return_val_if_fail(ctx != NULL, FALSE);
+
+       ctx->run_length_encoded =
+               ((ctx->hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR)
+                || (ctx->hdr->type == TGA_TYPE_RLE_TRUECOLOR)
+                || (ctx->hdr->type == TGA_TYPE_RLE_GRAYSCALE));
+
+       if (ctx->hdr->has_cmap)
+               ctx->cmap_size = ((ctx->hdr->cmap_bpp + 7) >> 3) *
+                       LE16(ctx->hdr->cmap_n_colors);
+
+       alpha = ((ctx->hdr->bpp == 32) ||
+                (ctx->hdr->has_cmap && (ctx->hdr->cmap_bpp == 32)));
+
+       ctx->pbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, alpha, 8,
+                                  LE16(ctx->hdr->width),
+                                  LE16(ctx->hdr->height));
+       if (!ctx->pbuf) {
+               g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate new pixbuf"));
+               return FALSE;
+       }
+       ctx->pbuf_bytes = ctx->pbuf->rowstride * ctx->pbuf->height;
+       ctx->pptr = ctx->pbuf->pixels;
+
+       if ((ctx->hdr->type == TGA_TYPE_PSEUDOCOLOR) ||
+           (ctx->hdr->type == TGA_TYPE_GRAYSCALE))
+               ctx->rowstride = ctx->pbuf->width;
+       else if (ctx->hdr->type == TGA_TYPE_TRUECOLOR)
+               ctx->rowstride = ctx->pbuf->rowstride;
+
+       return TRUE;
+}
+
+static void parse_data_for_row_pseudocolor(TGAContext *ctx)
+{
+       guchar *s = ctx->in->data;
+       guint upper_bound = ctx->pbuf->width;
+
+       for (; upper_bound; upper_bound--, s++) {
+               *ctx->pptr++ = ctx->cmap->cols[*s].r;
+               *ctx->pptr++ = ctx->cmap->cols[*s].g;
+               *ctx->pptr++ = ctx->cmap->cols[*s].b;
+               if (ctx->hdr->cmap_bpp == 32)
+                       *ctx->pptr++ = ctx->cmap->cols[*s].a;
+       }
+       ctx->pbuf_bytes_done += ctx->pbuf->n_channels * ctx->pbuf->width;
+       if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
+               ctx->done = TRUE;
+}
+
+static void swap_channels(TGAContext *ctx)
+{
+       register guchar swap;
+       register guint count;
+       for (count = ctx->pbuf->width; count; count--) {
+               swap = ctx->pptr[0];
+               ctx->pptr[0] = ctx->pptr[2];
+               ctx->pptr[2] = swap;
+               ctx->pptr += ctx->pbuf->n_channels;
+       }
+}
+
+static void parse_data_for_row_truecolor(TGAContext *ctx)
+{
+       g_memmove(ctx->pptr, ctx->in->data, ctx->pbuf->rowstride);
+       swap_channels(ctx);
+       ctx->pbuf_bytes_done += ctx->pbuf->rowstride;
+       if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
+               ctx->done = TRUE;
+}
+
+static void parse_data_for_row_grayscale(TGAContext *ctx)
+{
+       guchar *s = ctx->in->data;
+       guint upper_bound = ctx->pbuf->width;
+
+       for (; upper_bound; upper_bound--) {
+               ctx->pptr[0] = ctx->pptr[1] = ctx->pptr[2] = *s++;
+               ctx->pptr += 3;
+       }
+       ctx->pbuf_bytes_done = ctx->pbuf->width * 3;
+       if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
+               ctx->done = TRUE;
+}
+
+static gboolean parse_data_for_row(TGAContext *ctx, GError **err)
+{
+       if (ctx->hdr->type == TGA_TYPE_PSEUDOCOLOR)
+               parse_data_for_row_pseudocolor(ctx);
+       else if (ctx->hdr->type == TGA_TYPE_TRUECOLOR)
+               parse_data_for_row_truecolor(ctx);
+       else if (ctx->hdr->type == TGA_TYPE_GRAYSCALE)
+               parse_data_for_row_grayscale(ctx);
+       ctx->in = io_buffer_free_segment(ctx->in, ctx->rowstride, err);
+       if (!ctx->in)
+               return FALSE;
+       (*ctx->ufunc) (ctx->pbuf, 0,
+                      (ctx->pbuf_bytes_done / ctx->pbuf->rowstride) - 1,
+                      ctx->pbuf->width, 1, ctx->udata);
+       return TRUE;
+}
+
+static void write_rle_data(TGAContext *ctx, TGAColor *color, guint *rle_count)
+{
+       ctx->pbuf_bytes_done += ctx->pbuf->n_channels * (*rle_count);
+       for (; *rle_count; (*rle_count)--) {
+               g_memmove(ctx->pptr, (guchar *) color, ctx->pbuf->n_channels);
+               ctx->pptr += ctx->pbuf->n_channels;
+       }
+}
+
+static guint parse_rle_data_pseudocolor(TGAContext *ctx)
+{
+       guint rle_num, raw_num;
+       guchar *s, tag;
+       guint n;
+
+       g_return_val_if_fail(ctx->in->size > 0, 0);
+       s = ctx->in->data;
+
+       for (n = 0; n < ctx->in->size; ) {
+               tag = *s;
+               s++, n++;
+               if (tag & 0x80) {
+                       if (n == ctx->in->size) {
+                               return --n;
+                       } else {
+                               rle_num = (tag & 0x7f) + 1;
+                               write_rle_data(ctx, &ctx->cmap->cols[*s], &rle_num);
+                               s++, n++;
+                       }
+               } else {
+                       raw_num = tag + 1;
+                       if (n + raw_num >= ctx->in->size) {
+                               return --n;
+                       } else {
+                               for (; raw_num; raw_num--) {
+                                       *ctx->pptr++ =
+                                               ctx->cmap->cols[*s].r;
+                                       *ctx->pptr++ =
+                                               ctx->cmap->cols[*s].g;
+                                       *ctx->pptr++ =
+                                               ctx->cmap->cols[*s].b;
+                                       if (ctx->pbuf->n_channels == 4)
+                                               *ctx->pptr++ = ctx->cmap->cols[*s].a;
+                                       s++, n++;
+                                       ctx->pbuf_bytes_done += ctx->pbuf->n_channels;
+                               }
+                       }
+               }
+       }
+       if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
+               ctx->done = TRUE;
+       return n;
+}
+
+static void swap_channels_rle(TGAContext *ctx, guint count)
+{
+       register guchar swap;
+       for (; count; count--) {
+               swap = ctx->pptr[0];
+               ctx->pptr[0] = ctx->pptr[2];
+               ctx->pptr[2] = swap;
+               ctx->pptr += ctx->pbuf->n_channels;
+       }
+}
+
+static guint parse_rle_data_truecolor(TGAContext *ctx)
+{
+       TGAColor col;
+       guint rle_num, raw_num;
+       guchar *s, tag;
+       guint n = 0;
+
+       g_return_val_if_fail(ctx->in->size > 0, 0);
+       s = ctx->in->data;
+
+       for (n = 0; n < ctx->in->size; ) {
+               tag = *s;
+               s++, n++;
+               if (tag & 0x80) {
+                       if (n + ctx->pbuf->n_channels >= ctx->in->size) {
+                               return --n;
+                       } else {
+                               rle_num = (tag & 0x7f) + 1;
+                               col.b = *s++;
+                               col.g = *s++;
+                               col.r = *s++;
+                               if (ctx->hdr->bpp == 32)
+                                       col.a = *s++;
+                               write_rle_data(ctx, &col, &rle_num);
+                               n += ctx->pbuf->n_channels;
+                       }
+               } else {
+                       raw_num = tag + 1;
+                       if (n + (raw_num * ctx->pbuf->n_channels) >= ctx->in->size) {
+                               return --n;
+                       } else {
+                               g_memmove(ctx->pptr, s, raw_num * ctx->pbuf->n_channels);
+                               swap_channels_rle(ctx, raw_num);
+                               s += raw_num * ctx->pbuf->n_channels;
+                               n += raw_num * ctx->pbuf->n_channels;
+                               ctx->pbuf_bytes_done += raw_num * ctx->pbuf->n_channels;
+                       }
+               }
+       }
+       if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
+               ctx->done = TRUE;
+       return n;
+}
+
+static guint parse_rle_data_grayscale(TGAContext *ctx)
+{
+       TGAColor tone;
+       guint rle_num, raw_num;
+       guchar *s, tag;
+       guint n;
+
+       g_return_val_if_fail(ctx->in->size > 0, 0);
+       s = ctx->in->data;
+
+       for (n = 0; n < ctx->in->size; ) {
+               tag = *s;
+               s++, n++;
+               if (tag & 0x80) {
+                       if (n == ctx->in->size) {
+                               return --n;
+                       } else {
+                               rle_num = (tag & 0x7f) + 1;
+                               tone.r = tone.g = tone.b = *s;
+                               s++, n++;
+                               write_rle_data(ctx, &tone, &rle_num);
+                       }
+               } else {
+                       raw_num = tag + 1;
+                       if (n + raw_num >= ctx->in->size) {
+                               return --n;
+                       } else {
+                               for (; raw_num; raw_num--) {
+                                       ctx->pptr[0] = ctx->pptr[1] = ctx->pptr[2] = *s;
+                                       s++, n++;
+                                       ctx->pptr += 3;
+                                       ctx->pbuf_bytes_done += 3;
+                               }
+                       }
+               }
+       }
+       if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
+               ctx->done = TRUE;
+       return n;
+}
+
+static gboolean parse_rle_data(TGAContext *ctx, GError **err)
+{
+       guint count = 0;
+       guint pbuf_count = 0;
+       if (ctx->hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR) {
+               count = parse_rle_data_pseudocolor(ctx);
+               pbuf_count = count *ctx->pbuf->n_channels;
+       } else if (ctx->hdr->type == TGA_TYPE_RLE_TRUECOLOR) {
+               count = parse_rle_data_truecolor(ctx);
+               pbuf_count = count;
+       } else if (ctx->hdr->type == TGA_TYPE_RLE_GRAYSCALE) {
+               count = parse_rle_data_grayscale(ctx);
+               pbuf_count = count * 3;
+       }
+       ctx->in = io_buffer_free_segment(ctx->in, count, err);
+       if (!ctx->in)
+               return FALSE;
+       (*ctx->ufunc) (ctx->pbuf, 0, ctx->pbuf_bytes_done / ctx->pbuf->rowstride,
+                      ctx->pbuf->width, pbuf_count / ctx->pbuf->rowstride,
+                      ctx->udata);
+       return TRUE;
+}
+
+static gboolean try_colormap(TGAContext *ctx, GError **err)
+{
+       static guchar *p;
+       static guint n;
+
+       g_return_val_if_fail(ctx != NULL, FALSE);
+       g_return_val_if_fail(ctx->cmap_size > 0, TRUE);
+
+       ctx->cmap = g_try_malloc(sizeof(TGAColormap));
+       if (!ctx->cmap) {
+               g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate colormap structure"));
+               return FALSE;
+       }
+       ctx->cmap->size = LE16(ctx->hdr->cmap_n_colors);
+       ctx->cmap->cols = g_try_malloc(sizeof(TGAColor) * ctx->cmap->size);
+       if (!ctx->cmap->cols) {
+               g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate colormap entries"));
+               g_free(ctx->cmap);
+               return FALSE;
+       }
+
+       p = ctx->in->data;
+       for (n = 0; n < ctx->cmap->size; n++) {
+               if ((ctx->hdr->cmap_bpp == 15) || (ctx->hdr->cmap_bpp == 16)) {
+                       guint16 col = p[0] + (p[1] << 8);
+                       ctx->cmap->cols[n].b = (col >> 7) & 0xf8;
+                       ctx->cmap->cols[n].g = (col >> 2) & 0xf8;
+                       ctx->cmap->cols[n].r = col << 3;
+                       p += 2;
+               }
+               else if ((ctx->hdr->cmap_bpp == 24) || (ctx->hdr->cmap_bpp == 32)) {
+                       ctx->cmap->cols[n].b = *p++;
+                       ctx->cmap->cols[n].g = *p++;
+                       ctx->cmap->cols[n].r = *p++;
+                       if (ctx->hdr->cmap_bpp == 32)
+                               ctx->cmap->cols[n].a = *p++;
+               } else {
+                       g_set_error(err, GDK_PIXBUF_ERROR, 
+                                   GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                   _("Unexpected bitdepth for colormap entries"));
+                       g_free(ctx->cmap->cols);
+                       g_free(ctx->cmap);
+                       return FALSE;
+               }
+       }
+       ctx->in = io_buffer_free_segment(ctx->in, ctx->cmap_size, err);
+       if (!ctx->in)
+               return FALSE;
+       return TRUE;
+}
+
+static gboolean try_preload(TGAContext *ctx, GError **err)
+{
+       if (!ctx->hdr) {
+               if (ctx->in->size >= sizeof(TGAHeader)) {
+                       ctx->hdr = g_try_malloc(sizeof(TGAHeader));
+                       if (!ctx->hdr) {
+                               g_set_error(err, GDK_PIXBUF_ERROR,
+                                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                           _("Can't allocate TGA header memory"));
+                               return FALSE;
+                       }
+                       g_memmove(ctx->hdr, ctx->in->data, sizeof(TGAHeader));
+                       ctx->in = io_buffer_free_segment(ctx->in, sizeof(TGAHeader), err);
+                       if (!ctx->in)
+                               return FALSE;
+                       if (!fill_in_context(ctx, err))
+                               return FALSE;
+                       if (ctx->hdr->infolen > 255) {
+                               g_set_error(err, GDK_PIXBUF_ERROR,
+                                           GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                           _("TGA image comment length is too long"));
+                               return FALSE;
+                       }
+                       if ((ctx->hdr->flags & TGA_INTERLEAVE_MASK) != 
+                           TGA_INTERLEAVE_NONE ||
+                           ctx->hdr->flags & TGA_ORIGIN_RIGHT || 
+                           ctx->hdr->flags & TGA_ORIGIN_LOWER) {
+                               g_set_error(err, GDK_PIXBUF_ERROR, 
+                                           GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
+                                           _("TGA image type not supported"));
+                               return FALSE;
+                       }
+               } else {
+                       return TRUE;
+               }
+       }
+       if (!ctx->skipped_info) {
+               if (ctx->in->size >= ctx->hdr->infolen) {
+                       ctx->in = io_buffer_free_segment(ctx->in, ctx->hdr->infolen, err);
+                       if (!ctx->in)
+                               return FALSE;
+                       ctx->skipped_info = TRUE;
+               } else {
+                       return TRUE;
+               }
+       }
+       if (ctx->hdr->has_cmap && !ctx->cmap) {
+               if (ctx->in->size >= ctx->cmap_size) {
+                       if (!try_colormap(ctx, err))
+                               return FALSE;
+               } else {
+                       return TRUE;
+               }
+       }
+       if (!ctx->prepared) {
+               (*ctx->pfunc) (ctx->pbuf, NULL, ctx->udata);
+               ctx->prepared = TRUE;
+       }
+       /* We shouldn't get here anyway. */
+       return TRUE;
+}
+
+static gpointer gdk_pixbuf__tga_begin_load(ModulePreparedNotifyFunc f1,
+                                          ModuleUpdatedNotifyFunc f2,
+                                          gpointer udata, GError **err)
+{
+       TGAContext *ctx;
+
+       ctx = g_try_malloc(sizeof(TGAContext));
+       if (!ctx) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate memory for TGA context struct"));
+               return NULL;
+       }
+
+       ctx->hdr = NULL;
+       ctx->rowstride = 0;
+       ctx->run_length_encoded = FALSE;
+
+       ctx->cmap = NULL;
+       ctx->cmap_size = 0;
+
+       ctx->pbuf = NULL;
+       ctx->pbuf_bytes = 0;
+       ctx->pbuf_bytes_done = 0;
+       ctx->pptr = NULL;
+
+       ctx->in = io_buffer_new(err);
+       if (!ctx->in) {
+               g_free(ctx);
+               return NULL;
+       }
+
+       ctx->skipped_info = FALSE;
+       ctx->prepared = FALSE;
+       ctx->done = FALSE;
+
+       ctx->pfunc = f1;
+       ctx->ufunc = f2;
+       ctx->udata = udata;
+
+       return ctx;
+}
+
+static gboolean gdk_pixbuf__tga_load_increment(gpointer data,
+                                              const guchar *buffer,
+                                              guint size,
+                                              GError **err)
+{
+       TGAContext *ctx = (TGAContext*) data;
+       g_return_val_if_fail(ctx != NULL, FALSE);
+
+       if (ctx->done)
+               return TRUE;
+
+       g_return_val_if_fail(buffer != NULL, TRUE);
+       ctx->in = io_buffer_append(ctx->in, buffer, size, err);
+       if (!ctx->in)
+               return FALSE;
+       if (!ctx->prepared) {
+               if (!try_preload(ctx, err))
+                       return FALSE;
+               if (!ctx->prepared)
+                       return TRUE;
+               if (ctx->in->size == 0)
+                       return TRUE;
+       }
+
+       if (ctx->run_length_encoded) {
+               if (!parse_rle_data(ctx, err))
+                       return FALSE;
+       } else {
+               while (ctx->in->size >= ctx->rowstride)
+                       if (!parse_data_for_row(ctx, err))
+                               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean gdk_pixbuf__tga_stop_load(gpointer data, GError **err)
+{
+       TGAContext *ctx = (TGAContext *) data;
+       g_return_val_if_fail(ctx != NULL, FALSE);
+
+       g_free(ctx->hdr);
+       if (ctx->cmap)
+               g_free(ctx->cmap);
+       gdk_pixbuf_unref(ctx->pbuf);
+       if (ctx->in->size)
+               ctx->in = io_buffer_free_segment(ctx->in, ctx->in->size, err);
+       if (!ctx->in) {
+               g_free(ctx);
+               return FALSE;
+       }
+       io_buffer_free(ctx->in);
+       g_free(ctx);
+       return TRUE;
+}
+
+static TGAHeader *get_header_from_file(FILE *f, GError **err)
+{
+       TGAHeader *hdr;
+
+       if (!fseek_check(f, 0, SEEK_SET, err))
+               return NULL;
+       if (!(hdr = g_try_malloc(sizeof(TGAHeader)))) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate memory for TGA header"));
+               return NULL;
+       }
+       if (!fread_check(hdr, sizeof(TGAHeader), 1, f, err)) {
+               g_free(hdr);
+               return NULL;
+       }
+       if (hdr->infolen > 255) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                           _("Too big value in the infolen field of TGA header."));
+               g_free(hdr);
+               return NULL;
+       }
+
+       return hdr;
+}
+
+static TGAColormap *get_colormap_from_file(FILE *f, 
+                                          TGAHeader *hdr,
+                                          GError **err)
+{
+       TGAColormap *cmap;
+       guchar *pal_buf, *p;
+       guint n, pal_size;
+  
+       if (!fseek_check(f, sizeof(TGAHeader) + hdr->infolen, SEEK_SET, err))
+               return NULL;
+
+       pal_size = LE16(hdr->cmap_n_colors) * ((hdr->cmap_bpp + 7) >> 3);
+       pal_buf = g_try_malloc(pal_size);
+       if (!pal_buf) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate memory for TGA cmap temporary buffer"));
+               return NULL;
+       }
+       if (!fread_check(pal_buf, pal_size, 1, f, err)) {
+               g_free(pal_buf);
+               return NULL;
+       }
+       p = pal_buf;
+
+       if (!(cmap = g_try_malloc(sizeof(TGAColormap)))) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate memory for TGA colormap struct"));
+               g_free(pal_buf);
+               return NULL;
+       }
+       cmap->size = LE16(hdr->cmap_n_colors);
+       cmap->cols = g_try_malloc(sizeof(TGAColor) * cmap->size);
+       if (!cmap->cols) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate memory for TGA colormap entries"));
+               g_free(pal_buf);
+               g_free(cmap);
+               return NULL;
+       }
+
+       if (hdr->cmap_bpp != 15 && hdr->cmap_bpp != 16 &&
+           hdr->cmap_bpp != 24 && hdr->cmap_bpp != 32) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
+                           _("Unexpected bitdepth for TGA colormap"));
+               g_free(pal_buf);
+               g_free(cmap->cols);
+               g_free(cmap);
+               return NULL;
+       }
+
+       for (n = 0; n < cmap->size; n++) {
+               if ((hdr->cmap_bpp == 15) || (hdr->cmap_bpp == 16)) {
+                       guint16 col = p[0] + (p[1] << 8);
+                       p += 2;
+                       cmap->cols[n].b = (col >> 7) & 0xf8;
+                       cmap->cols[n].g = (col >> 2) & 0xf8;
+                       cmap->cols[n].r = col << 3;
+               } else if ((hdr->cmap_bpp == 24) || (hdr->cmap_bpp == 32)) {
+                       cmap->cols[n].b = *p++;
+                       cmap->cols[n].g = *p++;
+                       cmap->cols[n].r = *p++;
+                       if (hdr->cmap_bpp == 32)
+                               cmap->cols[n].a = *p++;
+               }
+       }
+
+       g_free(pal_buf);
+       return cmap;
+}
+
+static GdkPixbuf *get_image_pseudocolor(FILE *f, TGAHeader *hdr,
+                                       TGAColormap *cmap, gboolean rle,
+                                       GError **err)
+{
+       GdkPixbuf *pbuf;
+       guchar *p, color, tag;
+       glong n, image_offset;
+       guint count;
+
+       image_offset = sizeof(TGAHeader) + hdr->infolen;
+       if (!hdr->has_cmap) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                           _("Pseudo-Color image without colormap"));
+               return NULL;
+       } else {
+               image_offset += cmap->size * ((hdr->cmap_bpp + 7) >> 3);
+       }
+       if (!fseek_check(f, image_offset, SEEK_SET, err)) {
+               g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
+                           _("Can't seek to image offset -- end-of-file probably encountered"));
+               return NULL;
+       }
+
+       pbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, (hdr->cmap_bpp == 32), 8,
+                             LE16(hdr->width), LE16(hdr->height));
+       if (!pbuf) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate pixbuf"));
+               return NULL;
+       }
+       pbuf->destroy_fn = free_buffer;
+       pbuf->destroy_fn_data = NULL;
+       p = pbuf->pixels;
+
+       if (rle) {
+               n = count = 0;
+               for (; n < pbuf->width * pbuf->height;) {
+                       if (!fread_check(&tag, 1, 1, f, err)) {
+                               gdk_pixbuf_unref(pbuf);
+                               return NULL;
+                       }
+                       if (tag & 0x80) {
+                               count = (tag & 0x7f) + 1;
+                               n += count;
+                               if (!fread_check(&color, 1, 1, f, err)) {
+                                       gdk_pixbuf_unref(pbuf);
+                                       return NULL;
+                               }
+                               for (; count; count--) {
+                                       *p++ = cmap->cols[color].r;
+                                       *p++ = cmap->cols[color].g;
+                                       *p++ = cmap->cols[color].b;
+                                       if (hdr->cmap_bpp == 32)
+                                               *p++ = cmap->cols[color].a;
+                               }
+                       } else {
+                               count = tag + 1;
+                               n += count;
+                               for (; count; count--) {
+                                       if (!fread_check(&color, 1, 1, f, err)) {
+                                               gdk_pixbuf_unref(pbuf);
+                                               return NULL;
+                                       }
+                                       *p++ = cmap->cols[color].r;
+                                       *p++ = cmap->cols[color].g;
+                                       *p++ = cmap->cols[color].b;
+                                       if (hdr->cmap_bpp == 32)
+                                               *p++ = cmap->cols[color].a;
+                               }
+                       }
+               }
+       } else {
+               for (n = 0; n < pbuf->width * pbuf->height; n++) {
+                       if (!fread_check(&color, 1, 1, f, err)) {
+                               gdk_pixbuf_unref(pbuf);
+                               return NULL;
+                       }
+                       *p++ = cmap->cols[color].r;
+                       *p++ = cmap->cols[color].g;
+                       *p++ = cmap->cols[color].b;
+                       if (hdr->cmap_bpp == 32)
+                               *p++ = cmap->cols[color].a;
+               }
+       }
+
+       return pbuf;
+}
+
+static void swap_channels_pixbuf(GdkPixbuf *pbuf)
+{
+       guchar *p, swap;
+       glong n;
+
+       p = pbuf->pixels;
+       for (n = 0; n < pbuf->width * pbuf->height; n++) {
+               swap = p[0];
+               p[0] = p[2];
+               p[2] = swap;
+               p += pbuf->n_channels;
+       }
+}
+
+static GdkPixbuf *get_image_truecolor(FILE *f, TGAHeader *hdr,
+                                     gboolean rle, GError **err)
+{
+       GdkPixbuf *pbuf;
+       guchar *p, tag;
+       glong n, image_offset;
+       guint32 pixel;
+       guint count;
+
+       image_offset = sizeof(TGAHeader) + hdr->infolen;
+       /* A truecolor image shouldn't actually have a colormap. */
+       if (hdr->has_cmap)
+               image_offset += LE16(hdr->cmap_n_colors) * ((hdr->cmap_bpp + 7) >> 3);
+       if (!fseek_check(f, image_offset, SEEK_SET, err))
+               return NULL;
+
+       pbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, (hdr->bpp == 32), 8,
+                             LE16(hdr->width), LE16(hdr->height));
+       if (!pbuf) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate pixbuf"));
+               return NULL;
+       }
+       pbuf->destroy_fn = free_buffer;
+       pbuf->destroy_fn_data = NULL;
+       p = pbuf->pixels;
+
+       if (rle) {
+               n = count = 0;
+               for (; n < pbuf->width * pbuf->height;) {
+                       if (!fread_check(&tag, 1, 1, f, err)) {
+                               gdk_pixbuf_unref(pbuf);
+                               return NULL;
+                       }
+                       if (tag & 0x80) {
+                               count = (tag & 0x7f) + 1;
+                               n += count;
+                               if (!fread_check(&pixel, pbuf->n_channels, 1, f, err)) {
+                                       gdk_pixbuf_unref(pbuf);
+                                       return NULL;
+                               }
+                               for (; count; count--) {
+                                       g_memmove(p, &pixel, pbuf->n_channels);
+                                       p += pbuf->n_channels;
+                               }
+                       } else {
+                               count = tag + 1;
+                               n += count;
+                               if (!fread_check(p, pbuf->n_channels * count, 1, f, err)) {
+                                       gdk_pixbuf_unref(pbuf);
+                                       return NULL;
+                               }
+                               p += pbuf->n_channels * count;
+                       }
+               }
+       } else {
+               if (!fread_check(p, pbuf->rowstride * pbuf->height, 1, f, err)) {
+                       gdk_pixbuf_unref(pbuf);
+                       return NULL;
+               }
+       }
+
+       swap_channels_pixbuf(pbuf);
+       return pbuf;
+}
+
+static GdkPixbuf *get_image_grayscale(FILE *f, TGAHeader *hdr,
+                                     gboolean rle, GError **err)
+{
+       GdkPixbuf *pbuf;
+       glong n, image_offset;
+       guchar *p, color, tag;
+       guint count;
+
+       image_offset = sizeof(TGAHeader) + hdr->infolen;
+       /* A grayscale image shouldn't actually have a colormap. */
+       if (hdr->has_cmap)
+               image_offset += LE16(hdr->cmap_n_colors) * ((hdr->cmap_bpp + 7) >> 3);
+       if (!fseek_check(f, image_offset, SEEK_SET, err))
+               return NULL;
+
+       pbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
+                             LE16(hdr->width), LE16(hdr->height));
+       if (!pbuf) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                           _("Can't allocate pixbuf"));
+               return NULL;
+       }
+       pbuf->destroy_fn = free_buffer;
+       pbuf->destroy_fn_data = NULL;
+       p = pbuf->pixels;
+
+       if (rle) {
+               n = count = 0;
+               for (; n < pbuf->width * pbuf->height;) {
+                       if (!fread_check(&tag, 1, 1, f, err)) {
+                               gdk_pixbuf_unref(pbuf);
+                               return NULL;
+                       }
+                       if (tag & 0x80) {
+                               count = (tag & 0x7f) + 1;
+                               n += count;
+                               if (!fread_check(&color, 1, 1, f, err)) {
+                                       gdk_pixbuf_unref(pbuf);
+                                       return NULL;
+                               }
+                               for (; count; count--) {
+                                       p[0] = p[1] = p[2] = color;
+                                       p += 3;
+                               }
+                       } else {
+                               count = tag + 1;
+                               n += count;
+                               for (; count; count--) {
+                                       if (!fread_check(&color, 1, 1, f, err)) {
+                                               gdk_pixbuf_unref(pbuf);
+                                               return NULL;
+                                       }
+                                       p[0] = p[1] = p[2] = color;
+                                       p += 3;
+                               }
+                       }
+               }
+       } else {
+               for (n = 0; n < pbuf->width * pbuf->height; n++) {
+                       if (!fread_check(&color, 1, 1, f, err)) {
+                               gdk_pixbuf_unref(pbuf);
+                               return NULL;
+                       }
+                       p[0] = p[1] = p[2] = color;
+                       p += 3;
+               }
+       }
+
+       return pbuf;
+}
+
+static GdkPixbuf *gdk_pixbuf__tga_load(FILE *f, GError **err)
+{
+       TGAHeader *hdr;
+       TGAColormap *cmap;
+       GdkPixbuf *pbuf;
+
+       cmap = NULL;
+       hdr = get_header_from_file(f, err);
+       if (!hdr)
+               return NULL;
+       if ((hdr->flags & TGA_INTERLEAVE_MASK) != TGA_INTERLEAVE_NONE ||
+           hdr->flags & TGA_ORIGIN_RIGHT || hdr->flags & TGA_ORIGIN_LOWER) {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
+                           _("Unsupported TGA image type"));
+               g_free(hdr);
+               return NULL;
+       }
+  
+       if (hdr->has_cmap && ((hdr->type == TGA_TYPE_PSEUDOCOLOR) || 
+                             (hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR))) {
+               cmap = get_colormap_from_file(f, hdr, err);
+               if (!cmap) {
+                       g_free(hdr);
+                       return NULL;
+               }
+       }
+
+       if (hdr->type == TGA_TYPE_PSEUDOCOLOR)
+               pbuf = get_image_pseudocolor(f, hdr, cmap, FALSE, err);
+       else if (hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR)
+               pbuf = get_image_pseudocolor(f, hdr, cmap, TRUE, err);
+       else if (hdr->type == TGA_TYPE_TRUECOLOR)
+               pbuf = get_image_truecolor(f, hdr, FALSE, err);
+       else if (hdr->type == TGA_TYPE_RLE_TRUECOLOR)
+               pbuf = get_image_truecolor(f, hdr, TRUE, err);
+       else if (hdr->type == TGA_TYPE_GRAYSCALE)
+               pbuf = get_image_grayscale(f, hdr, FALSE, err);
+       else if (hdr->type == TGA_TYPE_RLE_GRAYSCALE)
+               pbuf = get_image_grayscale(f, hdr, TRUE, err);
+       else {
+               g_set_error(err, GDK_PIXBUF_ERROR, 
+                           GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
+                           _("Unsupported TGA image type"));
+               pbuf = NULL;
+       }
+  
+       if (cmap) {
+               g_free(cmap->cols);
+               g_free(cmap);
+       }
+       g_free(hdr);
+  
+       return pbuf;
+}
+
+void
+gdk_pixbuf__tga_fill_vtable (GdkPixbufModule *module)
+{
+       module->load = gdk_pixbuf__tga_load;
+       module->begin_load = gdk_pixbuf__tga_begin_load;
+       module->stop_load = gdk_pixbuf__tga_stop_load;
+       module->load_increment = gdk_pixbuf__tga_load_increment;
+}
diff --git a/gdk-pixbuf/pixbufloader_tga.def b/gdk-pixbuf/pixbufloader_tga.def
new file mode 100644 (file)
index 0000000..1505585
--- /dev/null
@@ -0,0 +1,2 @@
+EXPORTS
+       gdk_pixbuf__tga_fill_vtable